﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using Microsoft.Goldenlight.Diablo.Data;

namespace Microsoft.Goldenlight.Diablo.AnimationLoader
{
    public class Program
    {
        private const string baseAnimationPath = @"D:\UOKR\build\animationframe";

        public static void Main(string[] args)
        {
            using (var dataContext = new AnimationDataContext())
            {
                foreach (var body in dataContext.Bodies.Include("Animations").OrderBy(body => body.Id))
                {
                    Console.WriteLine("Writing Body: {0} {1} {2}", body.Id, body.EnglishName, body.ChineseName);
                    using (var currentContext = new AnimationDataContext())
                    {
                        var fileNames = Directory.GetFiles(string.Format(@"{0}\{1}\", baseAnimationPath, body.Id.ToString("000000")), "*.bin");
                        foreach (var fileName in fileNames)
                        {
                            byte actionId;
                            if (byte.TryParse(fileName.Replace(baseAnimationPath, "").Replace(body.Id.ToString("000000"), "").Replace(@"\", "").Split('.')[0], out actionId))
                            {
                                if (body.BodyTypeId != 3)
                                {
                                    actionId += 200;
                                }
                                var animation = currentContext.Animations.FirstOrDefault(anim => anim.BodyId == body.Id && anim.ActionId == actionId);
                                if (animation == null)
                                {
                                    animation = new Animation
                                                    {
                                                        Id = body.Id * 1000 + actionId,
                                                        BodyId = body.Id,
                                                        ActionId = actionId,
                                                    };
                                    currentContext.AddToAnimations(animation);
                                }

                                Console.WriteLine("\tWriting Animation: {0}", animation.Id);

                                using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                                {
                                    using (var reader = new BinaryReader(stream))
                                    {
                                        var header = ReadHeader(reader);
                                        if (header != null)
                                        {
                                            animation.MainCenterX = (short) (0 - header.BeginX);
                                            animation.MainCenterY = (short) (0 - header.BeginY);
                                            animation.MainWidth = (short) (header.EndX - header.BeginX);
                                            animation.MainHeight = (short) (header.EndY - header.BeginY);

                                            var colorEntries = ReadColors(reader, header.ColorsReadOffset, header.ColorsCount);
                                            for (var index = 0; index < colorEntries.Count; index++)
                                            {
                                                var colorEntry = colorEntries[index];
                                                var animationColor = currentContext.AnimationColors.FirstOrDefault(animColor => animColor.AnimationId == animation.Id && animColor.Number == index);
                                                if (animationColor == null)
                                                {
                                                    animationColor = new AnimationColor
                                                                         {
                                                                             Id = animation.Id * 1000 + index,
                                                                             AnimationId = animation.Id,
                                                                             Number = (short) index
                                                                         };
                                                    animation.AnimationColors.Add(animationColor);
                                                }
                                                animationColor.Red = colorEntry.Red;
                                                animationColor.Green = colorEntry.Green;
                                                animationColor.Blue = colorEntry.Blue;
                                                animationColor.Alpha = colorEntry.Alpha;
                                            }

                                            var pixelDataReadOffset = header.FramesReadOffset + (header.FramesCount * 0x10);
                                            var pixelDataSize = header.Length - pixelDataReadOffset;
                                            var pixelData = ReadPixelData(reader, pixelDataReadOffset, pixelDataSize);

                                            var frameEntries = ReadFrames(reader, header.FramesReadOffset, header.FramesCount);
                                            foreach (var frameEntry in frameEntries)
                                            {
                                                var animationFrameId = animation.Id * 1000 + frameEntry.Id;

                                                var animationFrame = currentContext.AnimationFrames.FirstOrDefault(animFrame => animFrame.Id == animationFrameId);
                                                if (animationFrame == null)
                                                {
                                                    animationFrame = new AnimationFrame
                                                                         {
                                                                             Id = animationFrameId
                                                                         };
                                                    animation.AnimationFrames.Add(animationFrame);
                                                }
                                                animationFrame.DirectionId = CalculateDirection(header.FramesCount, frameEntry.Id);
                                                animationFrame.Number = CalculateFrameNumber(header.FramesCount, frameEntry.Id);
                                                animationFrame.CenterX = (short) (0 - frameEntry.BeginX);
                                                animationFrame.CenterY = (short) (0 - frameEntry.BeginY);
                                                animationFrame.Width = (short) (frameEntry.EndX - frameEntry.BeginX);
                                                animationFrame.Height = (short) (frameEntry.EndY - frameEntry.BeginY);

                                                var animationImage = currentContext.AnimationImages.FirstOrDefault(animImage => animImage.Id == animationFrameId);
                                                if (animationImage == null)
                                                {
                                                    animationImage = new AnimationImage
                                                                         {
                                                                             Id = animationFrameId
                                                                         };
                                                    currentContext.AddToAnimationImages(animationImage);
                                                }
                                                animationImage.Bitmap = ReadBitmap(animation, frameEntry, colorEntries, pixelData, pixelDataReadOffset);

                                                animationFrame.AnimationImage = animationImage;
                                            }
                                        }
                                        else
                                        {
                                            Console.WriteLine("This is not AnimationFrame File!");
                                        }
                                    }
                                }
                            }
                        }
                        currentContext.SaveChanges();
                    }
                }
            }
        }

        private static HeaderEntry ReadHeader(BinaryReader reader)
        {
            var header = new HeaderEntry
                             {
                                 Head = reader.ReadInt32()
                             };
            if (header.Head != 0x554F4D41)
            {
                return null;
            }
            header.Version = reader.ReadInt32();
            header.Length = reader.ReadInt32();
            header.Id = reader.ReadInt32();
            header.BeginX = reader.ReadInt16();
            header.BeginY = reader.ReadInt16();
            header.EndX = reader.ReadInt16();
            header.EndY = reader.ReadInt16();
            header.ColorsCount = reader.ReadInt32();
            header.ColorsReadOffset = reader.ReadInt32();
            header.FramesCount = reader.ReadInt32();
            header.FramesReadOffset = reader.ReadInt32();
            return header;
        }

        private static List<ColorEntry> ReadColors(BinaryReader reader, int offset, int count)
        {
            var colorEntries = new List<ColorEntry>(count);
            reader.BaseStream.Seek(offset, SeekOrigin.Begin);
            for (var index = 0; index < count; index++)
            {
                var color = new ColorEntry
                                {
                                    Red = reader.ReadByte(),
                                    Green = reader.ReadByte(),
                                    Blue = reader.ReadByte(),
                                    Alpha = reader.ReadByte()
                                };
                colorEntries.Add(color);
            }
            return colorEntries;
        }

        private static List<FrameEntry> ReadFrames(BinaryReader reader, int offset, int count)
        {
            var frameEntries = new List<FrameEntry>(count);
            reader.BaseStream.Seek(offset, SeekOrigin.Begin);
            for (var index = 0; index < count; index++)
            {
                var frame = new FrameEntry
                                {
                                    Version = reader.ReadInt16(),
                                    Id = reader.ReadInt16(),
                                    BeginX = reader.ReadInt16(),
                                    BeginY = reader.ReadInt16(),
                                    EndX = reader.ReadInt16(),
                                    EndY = reader.ReadInt16(),
                                    PixelsRelativeOffset = reader.ReadInt32()
                                };
                frame.PixelsReadOffset = offset + (index * 0x10) + frame.PixelsRelativeOffset;
                frameEntries.Add(frame);
            }
            return frameEntries;
        }

        private static byte[] ReadPixelData(BinaryReader reader, int offset, int count)
        {
            reader.BaseStream.Seek(offset, SeekOrigin.Begin);
            return reader.ReadBytes(count);
        }

        private static byte[] ReadBitmap(Animation animation, FrameEntry frameEntry, List<ColorEntry> colorEntries, byte[] pixelData, int pixelDataReadOffset)
        {
            var frameWidth = frameEntry.EndX - frameEntry.BeginX;
            var frameHeight = frameEntry.EndY - frameEntry.BeginY;

            var stream = new MemoryStream();
            var bitmap = new Bitmap(frameWidth, frameHeight);
            for (var x = 0; x < frameWidth; x++)
            {
                for (var y = 0; y < frameHeight; y++)
                {
                    bitmap.SetPixel(x, y, Color.Transparent);
                }
            }

            var offset = frameEntry.PixelsReadOffset - pixelDataReadOffset;
            var currentX = 0;
            var currentY = 0;
            while (currentY < frameHeight)
            {
                var pixelOffset = pixelData[offset++];
                if (pixelOffset < 0x80)
                {
                    for (var index = 0; index < pixelOffset; index++)
                    {
                        MoveNextCoordinate(ref currentX, ref currentY, frameWidth, frameHeight);
                    }
                }
                else
                {
                    var factorInfo = pixelData[offset++];
                    var firstPixelFactor = factorInfo / 0x10;
                    var lastPixelFactor = factorInfo % 0x10;

                    byte colorIndex;
                    Color pixelColor;
                    Color currentPixelColor;
                    if (firstPixelFactor > 0)
                    {
                        colorIndex = pixelData[offset++];
                        pixelColor = colorEntries[colorIndex].PixelColor;
                        currentPixelColor = bitmap.GetPixel(currentX, currentY);
                        pixelColor = CombineColors(pixelColor, currentPixelColor, firstPixelFactor);
                        bitmap.SetPixel(currentX, currentY, pixelColor);
                        MoveNextCoordinate(ref currentX, ref currentY, frameWidth, frameHeight);
                    }
                    for (var index = 0; index < pixelOffset - 0x80; index++)
                    {
                        colorIndex = pixelData[offset++];
                        pixelColor = colorEntries[colorIndex].PixelColor;
                        bitmap.SetPixel(currentX, currentY, pixelColor);
                        MoveNextCoordinate(ref currentX, ref currentY, frameWidth, frameHeight);
                    }
                    if (lastPixelFactor > 0)
                    {
                        colorIndex = pixelData[offset++];
                        pixelColor = colorEntries[colorIndex].PixelColor;
                        currentPixelColor = bitmap.GetPixel(currentX, currentY);
                        pixelColor = CombineColors(pixelColor, currentPixelColor, lastPixelFactor);
                        bitmap.SetPixel(currentX, currentY, pixelColor);
                        MoveNextCoordinate(ref currentX, ref currentY, frameWidth, frameHeight);
                    }
                }
            }
            string path = string.Format(@"D:\UOKR\image\{0}\{1}", animation.BodyId.ToString("0000"), animation.ActionId.ToString("00"));
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            bitmap.Save(string.Format(@"{0}\{1}.png", path, frameEntry.Id), ImageFormat.Png);
            bitmap.Save(stream, ImageFormat.Png);
            return stream.ToArray();
        }

        private static byte CalculateDirection(int framesCount, int frameId)
        {
            var framesPerDirection = framesCount / 5;
            return (byte) ((frameId - 1) / framesPerDirection);
        }

        private static short CalculateFrameNumber(int framesCount, int frameId)
        {
            var framesPerDirection = framesCount / 5;
            return (short) ((frameId - 1) % framesPerDirection);
        }

        private static bool MoveNextCoordinate(ref int currentX, ref int currentY, int width, int height)
        {
            currentX++;
            if (currentX >= width)
            {
                currentX = 0;
                currentY++;
            }
            return currentY < height && currentX < width;
        }

        private static Color CombineColors(Color sourceColor, Color targetColor, int factor)
        {
            long sourceNumber = sourceColor.ToArgb();
            long targetNumber = targetColor.ToArgb();
            var sourceNumber01 = (sourceNumber & 0x00FF00FF) * factor;
            var targetNumber01 = (targetNumber & 0x00FF00FF) * (0x10 - factor);
            var combinedNumber01 = sourceNumber01 + targetNumber01;
            var sourceNumber02 = ((sourceNumber >> 4) & 0xFFF00FF0) * factor;
            var targetNumber02 = ((targetNumber >> 4) & 0xFFF00FF0) * (0x10 - factor);
            var combinedNumber02 = sourceNumber02 + targetNumber02;
            var sourceNumber03 = ((sourceNumber >> 4) & 0x0FF00FF0) * factor;
            var targetNumber03 = ((targetNumber >> 4) & 0x0FF00FF0) * (0x10 - factor);
            var combinedNumber03 = sourceNumber03 + targetNumber03;
            var combinedNumber = (((combinedNumber01 >> 4) ^ combinedNumber02) & 0x00FF00FF) ^ combinedNumber03;
            return Color.FromArgb((int) combinedNumber);
        }
    }
}